﻿////////////////////////////////////////////////////////////////
///
/// File: BinaryTmpExp.cs
/// Author: Lucio Davide Spano
/// Last update: 27 July 2012
/// Description: This file contains the definition of a binary
///              temporal expression 
/// To do: 
/// ------------------------------------------------------------
/// Copyright (c) 2012 Lucio Davide Spano 
///                     (lucio.davide.spano@isti.cnr.it)
/// 
/// The use and distribution terms for this software are 
/// contained in the file named license.txt, which can be found 
/// in the root of this distribution.
/// By using this software in any fashion, you are agreeing to 
/// be bound by the terms of this license.
///
/// You must not remove this notice, or any other, from this
/// software.
/// ------------------------------------------------------------
////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GestIT
{
    public class BinaryTmpExp : TmpExp
    {
        public TmpExp LeftOperand 
        {
            get { return leftOperand; }
            set 
            {
                leftOperand = value;
                if (value != null)
                {
                    leftOperand.OnStateChanged += new StateChangedHandler(BinaryTmpExp_OnStateChanged);
                    leftOperand.OnNotAcceptedToken += new NotAcceptedToken(onNotAcceptedToken);
                    leftOperand.Parent = this;
                }
            }
        }


        public override string ToString()
        {
            return "" + Operator + " " + this.GetHashCode();
        }
        
        public TmpExp RightOperand
        {
            get { return rightOperand; }
            set {
                rightOperand = value;
                if (value != null)
                {
                    rightOperand.OnStateChanged += new StateChangedHandler(BinaryTmpExp_OnStateChanged);
                    rightOperand.OnNotAcceptedToken += new NotAcceptedToken(onNotAcceptedToken);
                    rightOperand.Parent = this;
                }
            }
        }

        public TmpOperator Operator { get; set; }

        private bool leftCompleted, rightCompleted;
        private TmpExp leftOperand, rightOperand;

        public BinaryTmpExp()
        {
            this.Operator = TmpOperator.None;
            this.LeftOperand = null;
            this.RightOperand = null;
            this.type = ExpType.Binary;
        }

        public BinaryTmpExp(TmpExp leftOperand, TmpExp rightOperand, TmpOperator op)
            : this()
        {
            this.LeftOperand = leftOperand;
            this.RightOperand = rightOperand;
            this.Operator = op;
        }

        public override void SetExectState(ExecState state, Token token)
        {
            base.SetExectState(state, token);
            switch (state)
            {
                case ExecState.Disabled:
                    {
                        if (this.LeftOperand != null)
                        {
                            this.LeftOperand.SetExectState(ExecState.Disabled, token);
                        }

                        if (this.RightOperand != null)
                        {
                            this.RightOperand.SetExectState(ExecState.Disabled, token);
                        }
                    } break;

                case ExecState.Enabled:
                    {
                        switch (this.Operator)
                        {
                            case TmpOperator.SynchronizedParallel:
                                this.leftCompleted = false;
                                this.rightCompleted = false;
                                goto case TmpOperator.OrderIndependence;
                            case TmpOperator.ImmediateChoice: goto case TmpOperator.OrderIndependence;
                            case TmpOperator.Choice: goto case TmpOperator.OrderIndependence;
                            case TmpOperator.Parallel: goto case TmpOperator.OrderIndependence;
                            case TmpOperator.Disabling: goto case TmpOperator.OrderIndependence;
                            case TmpOperator.OrderIndependence:
                                if (this.LeftOperand != null)
                                {
                                    this.LeftOperand.SetExectState(ExecState.Enabled, token);
                                }
                                if (this.RightOperand != null)
                                {
                                    this.RightOperand.SetExectState(ExecState.Enabled, token);
                                }
                                break;
                            case TmpOperator.Enabling:
                                if (this.LeftOperand != null)
                                {
                                    this.LeftOperand.SetExectState(ExecState.Enabled, token);
                                }
                                break;
                        }

                    } break;

                default: break;

            }
        }

      
        public void onNotAcceptedToken(TmpExp sender, SimpleTmpExp originalSender, ExpEventArgs args, Token token)
        {
            switch (this.Operator)
            {
                case TmpOperator.Choice:
                    if (bothEnabled())
                    {
                        bool leftAccept = this.leftOperand.Accepts(args);
                        if (!leftAccept)
                        {
                            this.leftOperand.SetExectState(ExecState.Disabled, token);
                            this.leftOperand.RaiseError(this.leftOperand, args, token);
                        }
                    }
                    if (bothEnabled())
                    {
                        bool rightAccept = this.rightOperand.Accepts(args);
                        if (!rightAccept)
                        {
                            this.rightOperand.SetExectState(ExecState.Disabled, token);
                            this.rightOperand.RaiseError(this.rightOperand, args, token);
                        }
                    }

                    break;

                default: break;
            }

            this.RaiseOnNotAcceptedToken(originalSender, args, token);
        }

        public override bool Accepts(ExpEventArgs args)
        {
            switch (this.Operator)
            {
                case TmpOperator.ImmediateChoice: goto case TmpOperator.Disabling;
                case TmpOperator.Choice: goto case TmpOperator.Disabling;
                case TmpOperator.Parallel: goto case TmpOperator.Disabling;
                case TmpOperator.SynchronizedParallel: goto case TmpOperator.Disabling;
                case TmpOperator.Enabling: goto case TmpOperator.Disabling;
                case TmpOperator.Disabling:
                    {
                        if (this.leftOperand != null && this.leftOperand.Accepts(args))
                        {
                            return true;
                        }
                        if (this.rightOperand != null && this.rightOperand.Accepts(args))
                        {
                            return true;
                        }
                        return false;
                    }

                case TmpOperator.OrderIndependence: goto case TmpOperator.None;
                case TmpOperator.None: return false;

                default: return false;
 
            }
        }

        private bool bothEnabled()
        {
            return this.leftOperand != null && (this.leftOperand.State == ExecState.Enabled || this.leftOperand.State == ExecState.Performing) &&
                this.rightOperand != null && (this.rightOperand.State == ExecState.Enabled || this.rightOperand.State == ExecState.Performing);
        }

        private void BinaryTmpExp_OnStateChanged(TmpExp sender, ExpEventArgs args, Token token)
        {
            if (this.Operator != TmpOperator.None)
            {
                //this.SetExectState(sender.State, token);
                switch (sender.State)
                {
                    case ExecState.Error:
                        this.SetExectState(ExecState.Error, token);
                        RaiseError(this, args, token);
                        RaiseOnStateChanged(this, args, token);
                        if (this.Iterative && token != null)
                        {
                            // avoid enabling null tokens
                            this.SetExectState(ExecState.Enabled, token);
                        }
                        break;
                    case ExecState.Performing:
                        ApplyPerforming(sender, args, token);
                        break;
                    case ExecState.Completed:
                        ApplyComplete(sender, args, token);
                        break;

                        
                }
            }
        }

        private void ApplyPerforming(TmpExp exp, ExpEventArgs args, Token token)
        {
            switch (this.Operator)
            {
                case TmpOperator.ImmediateChoice:
                    if (IsLeftChild(exp))
                    {
                        // executes the left operand of a choice,
                        // disables the right operand
                        this.RightOperand.SetExectState(ExecState.Disabled, token);
                    }
                    else if (IsRightChild(exp))
                    {
                        // execute the right operand of a choice, 
                        // disable the left operand
                        this.LeftOperand.SetExectState(ExecState.Disabled, token);
                    }
                    this.SetExectState(ExecState.Performing, token);
                    RaisePerforming(this, args, token);
                    break;

                case TmpOperator.Choice:
                    // no action in this case, we wait until one of the two 
                    // operands completes
                    this.SetExectState(ExecState.Performing, token);
                    RaisePerforming(this, args, token);
                    RaiseOnStateChanged(this, args, token);
                    break;

                case TmpOperator.Parallel: goto case TmpOperator.SynchronizedParallel;
                case TmpOperator.SynchronizedParallel:
                    // nothing to do 
                    this.SetExectState(ExecState.Performing, token);
                    RaisePerforming(this, args, token);
                    RaiseOnStateChanged(this, args, token);
                    break;

                case TmpOperator.Disabling:
                    if (IsRightChild(exp))
                    {
                        // starting the execution of the right operand 
                        // disables the left one
                        this.LeftOperand.SetExectState(ExecState.Disabled, token);
                    }

                    this.SetExectState(ExecState.Performing, token);
                    RaisePerforming(this, args, token);
                    RaiseOnStateChanged(this, args, token);
                    break;

                case TmpOperator.Enabling:
                    if (IsLeftChild(exp))
                    {
                        // disable the right operand during the execution of the left one
                        this.RightOperand.SetExectState(ExecState.Disabled, token);
                    }
                    else if (IsRightChild(exp))
                    {
                        // disable the left operand, that should be already completed
                        this.LeftOperand.SetExectState(ExecState.Disabled, token);
                    }
                    this.SetExectState(ExecState.Performing, token);
                    RaisePerforming(this, args, token);
                    RaiseOnStateChanged(this, args, token);
                    break;

                default: break;
            }                
        }

        private void ApplyComplete(TmpExp exp, ExpEventArgs args, Token token)
        {
            switch (this.Operator)
            {
                case TmpOperator.Choice: goto case TmpOperator.ImmediateChoice;
                case TmpOperator.ImmediateChoice:
                    // if one operand has been executed, the other one should be 
                    // disabled
                    if (IsLeftChild(exp))
                    {
                        this.RightOperand.SetExectState(ExecState.Disabled, token);
                    }
                    else if (IsRightChild(exp))
                    {
                        this.LeftOperand.SetExectState(ExecState.Disabled, token);
                    }
                    // set the current execution state to complete
                    this.SetExectState(ExecState.Completed, token);
                    RaiseCompleted(this, args, token);
                    RaiseOnStateChanged(this, args, token);
                    break;

                case TmpOperator.SynchronizedParallel:
                    if (IsLeftChild(exp) || this.LeftOperand.Optional || this.LeftOperand.State == ExecState.Completed)
                    {
                        this.leftCompleted = true;
                    }

                    if (IsRightChild(exp) || this.RightOperand.Optional || this.RightOperand.State == ExecState.Completed)
                    {
                        this.rightCompleted = true;
                    }

                    if (leftCompleted && rightCompleted)
                    {
                        this.SetExectState(ExecState.Completed, token);
                        RaiseCompleted(this, args, token);
                        RaiseOnStateChanged(this, args, token);
                    }
                    else
                    {
                        this.SetExectState(ExecState.Performing, token);
                        RaisePerforming(this, args, token);
                        RaiseOnStateChanged(this, args, token);
                    }
                    break;

                case TmpOperator.Parallel:
                    // differently from synchronized parallel we do not 
                    // track the execution of the two operands
                    this.SetExectState(ExecState.Completed, token);
                    RaiseCompleted(this, args, token);
                    RaiseOnStateChanged(this, args, token);
                    break;

                case TmpOperator.Disabling:
                    if (IsRightChild(exp))
                    {
                        this.LeftOperand.SetExectState(ExecState.Disabled, token);
                        this.SetExectState(ExecState.Completed, token);
                        RaiseCompleted(this, args, token);
                        RaiseOnStateChanged(this, args, token);
                    }
                    else
                    {
                        this.SetExectState(ExecState.Performing, token);
                        RaisePerforming(this, args, token);
                        RaiseOnStateChanged(this, args, token);
                    }
                    break;

                case TmpOperator.Enabling:
                    if (IsLeftChild(exp))
                    {
                        if (this.RightOperand.Optional)
                        {
                            this.SetExectState(ExecState.Completed, token);
                            this.RightOperand.SetExectState(ExecState.Completed, token);
                            RaiseCompleted(this, args, token);
                            RaiseOnStateChanged(this, args, token);
                        }
                        else
                        {
                            this.RightOperand.SetExectState(ExecState.Enabled, token);
                            this.SetExectState(ExecState.Performing, token);
                            RaisePerforming(this, args, token);
                            RaiseOnStateChanged(this, args, token);
                        }
                    }
                    else if (IsRightChild(exp))
                    {
                        this.SetExectState(ExecState.Completed, token);
                        RaiseCompleted(this, args, token);
                        RaiseOnStateChanged(this, args, token);
                    }
                    break;

                default: break;
            }

            if (this.Iterative && (this.state == ExecState.Completed || this.state == ExecState.Error))
            {
                this.SetExectState(ExecState.Enabled, token);
            }
        }

        private bool IsLeftChild(TmpExp exp)
        {
            if (this.LeftOperand != null && this.LeftOperand == exp)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private bool IsRightChild(TmpExp exp)
        {
            if (this.RightOperand != null && this.RightOperand == exp)
            {
                return true;
            }
            else
            {
                return false;
            }
        }




        

    }
}
